$opts->add( 'target', '' );
$opts->add( 'namespace', '', FormOptions::INTNULL );
- $opts->add( 'limit', 50 );
- $opts->add( 'from', 0 );
- $opts->add( 'back', 0 );
$opts->add( 'hideredirs', false );
$opts->add( 'hidetrans', false );
$opts->add( 'hidelinks', false );
$opts->add( 'hideimages', false );
$opts->fetchValuesFromRequest( $this->request );
- $opts->validateIntBounds( 'limit', 0, 5000 );
// Give precedence to subpage syntax
if ( isset($this->par) ) {
$wgOut->setPageTitle( wfMsgExt( 'whatlinkshere-title', 'escape', $this->target->getPrefixedText() ) );
$wgOut->setSubtitle( wfMsgHtml( 'linklistsub' ) );
- $wgOut->addHTML( wfMsgExt( 'whatlinkshere-barrow', array( 'escapenoentities') ) . ' ' .$this->skin->makeLinkObj($this->target, '', 'redirect=no' )."<br />\n");
+ $wgOut->addHTML( wfMsgExt( 'whatlinkshere-barrow', array( 'escapenoentities') ) . ' ' .
+ $this->skin->makeLinkObj($this->target, '', 'redirect=no' )."<br />\n");
- $this->showIndirectLinks( 0, $this->target, $opts->getValue( 'limit' ),
- $opts->getValue( 'from' ), $opts->getValue( 'back' ) );
+ $this->showIndirectLinks( 0, $this->target );
}
/**
* @param int $level Recursion level
* @param Title $target Target title
- * @param int $limit Number of entries to display
- * @param Title $from Display from this article ID
- * @param Title $back Display from this article ID at backwards scrolling
- * @private
+ * @param int $limit Result limit (used for redirects)
+ * For the top level, this outputs the link list to $wgOut.
+ * For sublevels, it returns the string of the HTML.
+ * @returns mixed (true/string)
*/
- function showIndirectLinks( $level, $target, $limit, $from = 0, $back = 0 ) {
- global $wgOut, $wgMaxRedirectLinksRetrieved;
+ function showIndirectLinks( $level, $target, $limit=false ) {
+ global $wgOut;
$dbr = wfGetDB( DB_SLAVE );
$options = array();
$hideimages = $target->getNamespace() != NS_IMAGE || $this->opts->getValue( 'hideimages' );
$fetchlinks = (!$hidelinks || !$hideredirs);
-
- // Make the query
- $plConds = array(
- 'page_id=pl_from',
- 'pl_namespace' => $target->getNamespace(),
- 'pl_title' => $target->getDBkey(),
- );
- if( $hideredirs ) {
- $plConds['page_is_redirect'] = 0;
- } elseif( $hidelinks ) {
- $plConds['page_is_redirect'] = 1;
- }
-
- $tlConds = array(
- 'page_id=tl_from',
- 'tl_namespace' => $target->getNamespace(),
- 'tl_title' => $target->getDBkey(),
- );
-
- $ilConds = array(
- 'page_id=il_from',
- 'il_to' => $target->getDBkey(),
- );
-
+
$namespace = $this->opts->getValue( 'namespace' );
- if ( is_int($namespace) ) {
- $plConds['page_namespace'] = $namespace;
- $tlConds['page_namespace'] = $namespace;
- $ilConds['page_namespace'] = $namespace;
- }
-
- if ( $from ) {
- $tlConds[] = "tl_from >= $from";
- $plConds[] = "pl_from >= $from";
- $ilConds[] = "il_from >= $from";
- }
-
- // Read an extra row as an at-end check
- $queryLimit = $limit + 1;
-
- // Enforce join order, sometimes namespace selector may
- // trigger filesorts which are far less efficient than scanning many entries
- $options[] = 'STRAIGHT_JOIN';
-
- $options['LIMIT'] = $queryLimit;
- $fields = array( 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' );
-
- if( $fetchlinks ) {
- $options['ORDER BY'] = 'pl_from';
- $plRes = $dbr->select( array( 'pagelinks', 'page' ), $fields,
- $plConds, __METHOD__, $options );
- }
-
- if( !$hidetrans ) {
- $options['ORDER BY'] = 'tl_from';
- $tlRes = $dbr->select( array( 'templatelinks', 'page' ), $fields,
- $tlConds, __METHOD__, $options );
- }
-
- if( !$hideimages ) {
- $options['ORDER BY'] = 'il_from';
- $ilRes = $dbr->select( array( 'imagelinks', 'page' ), $fields,
- $ilConds, __METHOD__, $options );
- }
+
+ $pager = new WhatLinksHerePager( $this, $target, $level, $limit, $namespace,
+ $hidelinks, $hideredirs, $hidetrans, $hideimages );
- if( ( !$fetchlinks || !$dbr->numRows($plRes) ) && ( $hidetrans || !$dbr->numRows($tlRes) ) && ( $hideimages || !$dbr->numRows($ilRes) ) ) {
+ if( !$pager->getNumRows() ) {
if ( 0 == $level ) {
$wgOut->addHTML( $this->whatlinkshereForm() );
$errMsg = is_int($namespace) ? 'nolinkshere-ns' : 'nolinkshere';
}
return;
}
-
- // Read the rows into an array and remove duplicates
- // templatelinks comes second so that the templatelinks row overwrites the
- // pagelinks row, so we get (inclusion) rather than nothing
- if( $fetchlinks ) {
- while ( $row = $dbr->fetchObject( $plRes ) ) {
- $row->is_template = 0;
- $row->is_image = 0;
- $rows[$row->page_id] = $row;
- }
- $dbr->freeResult( $plRes );
-
- }
- if( !$hidetrans ) {
- while ( $row = $dbr->fetchObject( $tlRes ) ) {
- $row->is_template = 1;
- $row->is_image = 0;
- $rows[$row->page_id] = $row;
- }
- $dbr->freeResult( $tlRes );
- }
- if( !$hideimages ) {
- while ( $row = $dbr->fetchObject( $ilRes ) ) {
- $row->is_template = 0;
- $row->is_image = 1;
- $rows[$row->page_id] = $row;
- }
- $dbr->freeResult( $ilRes );
- }
-
- // Sort by key and then change the keys to 0-based indices
- ksort( $rows );
- $rows = array_values( $rows );
-
- $numRows = count( $rows );
-
- // Work out the start and end IDs, for prev/next links
- if ( $numRows > $limit ) {
- // More rows available after these ones
- // Get the ID from the last row in the result set
- $nextId = $rows[$limit]->page_id;
- // Remove undisplayed rows
- $rows = array_slice( $rows, 0, $limit );
- } else {
- // No more rows after
- $nextId = false;
+
+ // If this is a sub-level, return out a string
+ if( $level > 0 ) {
+ return $pager->getBody();
}
- $prevId = $from;
- if ( $level == 0 ) {
+ if( $level == 0 ) {
$wgOut->addHTML( $this->whatlinkshereForm() );
$wgOut->addHTML( $this->getFilterPanel() );
$wgOut->addWikiMsg( 'linkshere', $this->target->getPrefixedText() );
-
- $prevnext = $this->getPrevNext( $prevId, $nextId );
- $wgOut->addHTML( $prevnext );
+ $wgOut->addHTML( '<hr/>' );
+ // Add nav links only to top list
+ $wgOut->addHTML( $pager->getNavigationBar() );
}
-
- $wgOut->addHTML( $this->listStart() );
- foreach ( $rows as $row ) {
- $nt = Title::makeTitle( $row->page_namespace, $row->page_title );
-
- if ( $row->page_is_redirect && $level < 2 ) {
- $wgOut->addHTML( $this->listItem( $row, $nt, true ) );
- $this->showIndirectLinks( $level + 1, $nt, $wgMaxRedirectLinksRetrieved );
- $wgOut->addHTML( Xml::closeElement( 'li' ) );
- } else {
- $wgOut->addHTML( $this->listItem( $row, $nt ) );
- }
- }
-
- $wgOut->addHTML( $this->listEnd() );
-
- if( $level == 0 ) {
- $wgOut->addHTML( $prevnext );
+
+ $wgOut->addHTML( $pager->getBody() );
+
+ if ( $level == 0 ) {
+ // Add nav links only to top list
+ $wgOut->addHTML( $pager->getNavigationBar() );
}
+ return true;
}
- protected function listStart() {
- return Xml::openElement( 'ul' );
- }
-
- protected function listItem( $row, $nt, $notClose = false ) {
+ public function listItem( $row, $nt, $notClose = false ) {
# local message cache
static $msgcache = null;
if ( $msgcache === null ) {
$props[] = $msgcache['isredirect'];
if ( $row->is_template )
$props[] = $msgcache['istemplate'];
- if( $row->is_image )
+ if ( $row->is_image )
$props[] = $msgcache['isimage'];
if ( count( $props ) ) {
Xml::tags( 'li', null, "$link $propsText $wlh" ) . "\n";
}
- protected function listEnd() {
- return Xml::closeElement( 'ul' );
- }
-
protected function wlhLink( Title $target, $text ) {
static $title = null;
if ( $title === null )
return $this->skin->makeKnownLinkObj( $this->selfTitle, $text, $query );
}
- function getPrevNext( $prevId, $nextId ) {
- global $wgLang;
- $currentLimit = $this->opts->getValue( 'limit' );
- $fmtLimit = $wgLang->formatNum( $currentLimit );
- $prev = wfMsgExt( 'whatlinkshere-prev', array( 'parsemag', 'escape' ), $fmtLimit );
- $next = wfMsgExt( 'whatlinkshere-next', array( 'parsemag', 'escape' ), $fmtLimit );
-
- $changed = $this->opts->getChangedValues();
- unset($changed['target']); // Already in the request title
-
- if ( 0 != $prevId ) {
- $overrides = array( 'from' => $this->opts->getValue( 'back' ) );
- $prev = $this->makeSelfLink( $prev, wfArrayToCGI( $overrides, $changed ) );
- }
- if ( 0 != $nextId ) {
- $overrides = array( 'from' => $nextId, 'back' => $prevId );
- $next = $this->makeSelfLink( $next, wfArrayToCGI( $overrides, $changed ) );
- }
-
- $limitLinks = array();
- foreach ( $this->limits as $limit ) {
- $prettyLimit = $wgLang->formatNum( $limit );
- $overrides = array( 'limit' => $limit );
- $limitLinks[] = $this->makeSelfLink( $prettyLimit, wfArrayToCGI( $overrides, $changed ) );
- }
-
- $nums = implode ( ' | ', $limitLinks );
-
- return wfMsgHtml( 'viewprevnext', $prev, $next, $nums );
- }
-
function whatlinkshereForm() {
global $wgScript, $wgTitle;
// We get nicer value from the title object
$this->opts->consumeValue( 'target' );
- // Reset these for new requests
- $this->opts->consumeValues( array( 'back', 'from' ) );
$target = $this->target ? $this->target->getPrefixedText() : '';
$namespace = $this->opts->consumeValue( 'namespace' );
return Xml::fieldset( wfMsg( 'whatlinkshere-filters' ), implode( ' | ', $links ) );
}
}
+
+/**
+ * Pager for Special:WhatLinksHere
+ * @ingroup SpecialPage Pager
+ */
+class WhatLinksHerePager extends AlphabeticPager {
+ protected $form, $target, $level, $limit, $namespace, $hidelinks, $hideredirs, $hidetrans, $hideimages;
+
+ function __construct( $form, $target, $level, $limit, $namespace, $hidelinks, $hideredirs, $hidetrans, $hideimages ) {
+ parent::__construct();
+ $this->mForm = $form;
+
+ $this->target = $target;
+ $this->namespace = $namespace;
+ $this->hidelinks = (bool)$hidelinks;
+ $this->hideredirs = (bool)$hideredirs;
+ $this->hidetrans = (bool)$hidetrans;
+ $this->hideimages = (bool)$hideimages;
+
+ $this->upperLimit = $limit; // limit cannot pass this
+ $this->level = intval($level); // recursion depth
+ }
+
+ function getQueryInfo() {
+ $queryInfo = array();
+ $options = array();
+
+ $fields = array( 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' );
+
+ $plConds = array(
+ 'page_id=pl_from',
+ 'pl_namespace' => $this->target->getNamespace(),
+ 'pl_title' => $this->target->getDBkey(),
+ );
+ if( $this->hideredirs ) {
+ $plConds['page_is_redirect'] = 0;
+ } else if( $this->hidelinks ) {
+ $plConds['page_is_redirect'] = 1;
+ }
+
+ $tlConds = array(
+ 'page_id=tl_from',
+ 'tl_namespace' => $this->target->getNamespace(),
+ 'tl_title' => $this->target->getDBkey(),
+ );
+
+ $ilConds = array(
+ 'page_id=il_from',
+ 'il_to' => $this->target->getDBkey(),
+ );
+
+ if( is_int($this->namespace) ) {
+ $plConds['page_namespace'] = $this->namespace;
+ $tlConds['page_namespace'] = $this->namespace;
+ $ilConds['page_namespace'] = $this->namespace;
+ }
+
+ $plOptions['ORDER BY'] = 'pl_from';
+ $tlOptions['ORDER BY'] = 'tl_from';
+ $ilOptions['ORDER BY'] = 'il_from';
+
+ // Enforce join order, sometimes namespace selector may
+ // trigger filesorts which are far less efficient than scanning many entries
+ $plOptions['USE INDEX'] = array('pagelinks' => 'pl_namespace');
+ $tlOptions['USE INDEX'] = array('templatelinks' => 'tl_namespace');
+ $ilOptions['USE INDEX'] = array('imagelinks' => 'il_to');
+
+ $fetchlinks = (!$this->hidelinks || !$this->hideredirs);
+ if( $fetchlinks ) {
+ $plQueryInfo = array(
+ 'tables' => array( 'pagelinks', 'page' ),
+ 'fields' => array_merge($fields,array('0 AS is_template','0 AS is_image')),
+ 'conds' => $plConds,
+ 'options' => array_merge($options,$plOptions)
+ );
+ $queryInfo[] = $plQueryInfo;
+ }
+
+ if( !$this->hidetrans ) {
+ $tlQueryInfo = array(
+ 'tables' => array( 'templatelinks', 'page' ),
+ 'fields' => array_merge($fields,array('1 AS is_template','0 AS is_image')),
+ 'conds' => $tlConds,
+ 'options' => array_merge($options,$tlOptions)
+ );
+ $queryInfo[] = $tlQueryInfo;
+ }
+
+ if( !$this->hideimages ) {
+ $ilQueryInfo = array(
+ 'tables' => array( 'imagelinks', 'page' ),
+ 'fields' => array_merge($fields,array('0 AS is_template','1 AS is_image')),
+ 'conds' => $ilConds,
+ 'options' => array_merge($options,$ilOptions)
+ );
+ $queryInfo[] = $ilQueryInfo;
+ }
+ return $queryInfo;
+ }
+
+ /**
+ * Do a query with specified parameters, rather than using the object
+ * context
+ *
+ * @param string $offset Index offset, inclusive
+ * @param integer $limit Exact query limit
+ * @param boolean $descending Query direction, false for ascending, true for descending
+ * @return ResultWrapper
+ */
+ function reallyDoQuery( $offset, $limit, $descending ) {
+ $fname = __METHOD__ . ' (' . get_class( $this ) . ')';
+ $queryInfo = $this->getQueryInfo();
+ # Make sure a valid wrapper is always returned!
+ if( empty($queryInfo) ) {
+ return new ResultWrapper( $this->mDb, false );
+ }
+ $SQLqueries = array();
+ // Redirect subqueries have an upper limit for perfomance.
+ // If one is given, override the requested limit with this if smaller.
+ $limit = $this->upperLimit ? min( $limit, $this->upperLimit ) : $limit;
+ // Some code here lifted from Pager.php
+ foreach( $queryInfo as $info ) {
+ $tables = $info['tables'];
+ $fields = $info['fields'];
+ $conds = isset( $info['conds'] ) ? $info['conds'] : array();
+ $options = isset( $info['options'] ) ? $info['options'] : array();
+ $join_conds = isset( $info['join_conds'] ) ? $info['join_conds'] : array();
+ if ( $descending ) {
+ $operator = '>';
+ } else {
+ $options['ORDER BY'] .= ' DESC';
+ $operator = '<';
+ }
+ if ( $offset != '' ) {
+ $conds[] = $this->mIndexField . $operator . $this->mDb->addQuotes( $offset );
+ }
+ $options['LIMIT'] = intval( $limit );
+ $SQLqueries[] = '('.$this->mDb->selectSQLText( $tables, $fields, $conds, $fname, $options, $join_conds ).')';
+ }
+ // Contruct the final query. UNION the mini-queries and merge the results.
+ // Remove duplicates within the result set.
+ $SQL = implode(' UNION DISTINCT ',$SQLqueries);
+ // Use proper order of result set
+ $SQL .= $descending ? " ORDER BY {$this->mIndexField} DESC" : " ORDER BY {$this->mIndexField}";
+ // Cut off at the specified limit
+ $SQL .= ' LIMIT ' . intval( $limit );
+ # Run the query!
+ $res = $this->mDb->query( $SQL, __METHOD__ );
+ return new ResultWrapper( $this->mDb, $res );
+ }
+
+ function getIndexField() {
+ return 'page_id';
+ }
+
+ function getStartBody() {
+ wfProfileIn( __METHOD__ );
+ # Do a link batch query
+ $lb = new LinkBatch;
+ while( $row = $this->mResult->fetchObject() ) {
+ $lb->add( $row->page_namespace, $row->page_title );
+ }
+ $lb->execute();
+ wfProfileOut( __METHOD__ );
+ return "<ul>\n";
+ }
+
+ function getEndBody() {
+ return "</ul>\n";
+ }
+
+ function formatRow( $row ) {
+ global $wgMaxRedirectLinksRetrieved;
+ $nt = Title::makeTitle( $row->page_namespace, $row->page_title );
+ # For redirects, recursively list out the links there...unless
+ # we hit the maxixum recurision depth.
+ if( $row->page_is_redirect && $this->level < 2 ) {
+ return $this->mForm->listItem( $row, $nt, true ) .
+ $this->mForm->showIndirectLinks( $this->level + 1, $nt, $wgMaxRedirectLinksRetrieved ) .
+ Xml::closeElement( 'li' );
+ } else {
+ return $this->mForm->listItem( $row, $nt );
+ }
+ }
+}